package cn.iocoder.yudao.module.hs.service.shiporder;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.hs.controller.admin.bigscreen.vo.WarehouseMaterialCount;
import cn.iocoder.yudao.module.hs.controller.admin.entryorder.vo.EntryOrderTrayRespVO;
import cn.iocoder.yudao.module.hs.controller.admin.shiporder.vo.*;
import cn.iocoder.yudao.module.hs.controller.admin.shiporderdetail.vo.ShipOrderDetailSaveReqVO;
import cn.iocoder.yudao.module.hs.dal.dataobject.customer.CustomerDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.material.MaterialDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.materialmode.MaterialModeDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.picktask.PickTaskDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.picktaskmaterialdetail.PickTaskMaterialDetailDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.shiporder.ShipOrderDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.shiporderdetail.ShipOrderDetailDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.warehouse.WarehouseDO;
import cn.iocoder.yudao.module.hs.dal.mysql.shiporder.ShipOrderMapper;
import cn.iocoder.yudao.module.hs.dal.mysql.shiporderdetail.ShipOrderDetailMapper;
import cn.iocoder.yudao.module.hs.dal.redis.RedisKeyConstants;
import cn.iocoder.yudao.module.hs.dal.redis.no.OrderNoRedisDAO;
import cn.iocoder.yudao.module.hs.enums.material.MaterialOperateTypeEnum;
import cn.iocoder.yudao.module.hs.enums.materialmode.MaterialModeConstants;
import cn.iocoder.yudao.module.hs.enums.shiporder.ShipOrderStatusEnum;
import cn.iocoder.yudao.module.hs.enums.warehouse.WarehouseTypeEnum;
import cn.iocoder.yudao.module.hs.framework.core.annotations.MaterialLog;
import cn.iocoder.yudao.module.hs.framework.core.utils.MaterialLogUtils;
import cn.iocoder.yudao.module.hs.service.customer.CustomerService;
import cn.iocoder.yudao.module.hs.service.material.MaterialService;
import cn.iocoder.yudao.module.hs.service.materialmode.MaterialModeService;
import cn.iocoder.yudao.module.hs.service.picktask.PickTaskService;
import cn.iocoder.yudao.module.hs.service.picktaskmaterial.PickTaskMaterialService;
import cn.iocoder.yudao.module.hs.service.picktaskmaterialdetail.PickTaskMaterialDetailService;
import cn.iocoder.yudao.module.hs.service.rfid.RfidService;
import cn.iocoder.yudao.module.hs.service.shiporderdetail.ShipOrderDetailService;
import cn.iocoder.yudao.module.hs.service.warehouse.WarehouseService;
import cn.iocoder.yudao.module.hs.util.RedisUtil;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.iocoder.yudao.module.system.enums.ErrorCodeConstants;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.hs.enums.ErrorCodeConstants.*;

/**
 * 出库单 Service 实现类
 *
 * @author 航盛
 */
@Service
@Validated
public class ShipOrderServiceImpl implements ShipOrderService {

    @Resource
    private ShipOrderMapper shipOrderMapper;
    @Resource
    private MaterialService materialService;
    @Resource
    private ShipOrderDetailService shipOrderDetailService;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private OrderNoRedisDAO orderNoRedisDAO;
    @Resource
    private WarehouseService warehouseService;
    @Resource
    private ShipOrderDetailMapper shipOrderDetailMapper;
    @Resource
    private RfidService rfidService;
    @Resource
    @Lazy
    private PickTaskService pickTaskService;
    @Resource
    private PickTaskMaterialService pickTaskMaterialService;
    @Resource
    private PickTaskMaterialDetailService pickTaskMaterialDetailService;
    @Resource
    private MaterialModeService materialModeService;
    @Resource
    private CustomerService customerService;

    @Override
    @Transactional
    public Long createShipOrder(ShipOrderSaveReqVO createReqVO) {
        ShipOrderDO shipOrderDO = this.getShipOrder(createReqVO.getCode());
        if (shipOrderDO != null) {
            throw exception(SHIP_ORDER_EXISTS);
        }
        if (CollUtil.isEmpty(createReqVO.getShipOrderDetailList())) {
            throw exception(SHIP_ORDER_DETAIL_EMPTY);
        }
        WarehouseDO warehouse = warehouseService.getFromWarehouse(createReqVO.getFromWarehouseCode());
        if (warehouse == null) {
            throw exception(WAREHOUSE_NOT_EXISTS);
        }
        createReqVO.setWarehouseType(warehouse.getWarehouseType());
        createReqVO.setFromWarehouseCode(createReqVO.getFromWarehouseCode());
        createReqVO.setWarehouseId(warehouse.getId());
        createReqVO.setDeptId(warehouse.getDeptId());
        WarehouseDO toWarehouse = warehouseService.getWarehouse(createReqVO.getToWarehouseCode());
        if (toWarehouse != null) {
            createReqVO.setToWarehouseId(toWarehouse.getId());
            for (ShipOrderDetailSaveReqVO shipOrderDetailSaveReqVO : createReqVO.getShipOrderDetailList()) {
                MaterialModeDO materialMode = materialModeService.getMaterialMode(shipOrderDetailSaveReqVO.getModeCode(), toWarehouse.getId());
                if (materialMode != null) {
                    if (MaterialModeConstants.IS_DIRECT_YES.equals(materialMode.getIsDirect())) {
                        createReqVO.setIsDirect(MaterialModeConstants.IS_DIRECT_YES);
                        CustomerDO customer = customerService.getCustomer(materialMode.getCustomerId());
                        if (customer != null) {
                            createReqVO.setCustomerId(materialMode.getCustomerId());
                            createReqVO.setCustomerCode(customer.getCode());
                        }
                    } else {
                        createReqVO.setIsDirect(MaterialModeConstants.IS_DIRECT_NO);
                    }
                }
            }
        } else {
            CustomerDO customer = customerService.getCustomer(createReqVO.getToWarehouseCode());
            if (customer != null) {
                createReqVO.setCustomerId(customer.getId());
                createReqVO.setCustomerCode(customer.getCode());
            }
            createReqVO.setIsDirect(MaterialModeConstants.IS_DIRECT_YES);
        }
        createReqVO.setStatus(ShipOrderStatusEnum.NEW.getStatus());
        ShipOrderDO shipOrder = BeanUtils.toBean(createReqVO, ShipOrderDO.class);
        shipOrderMapper.insert(shipOrder);

        // 插入发货单明细
        List<ShipOrderDetailDO> shipOrderDetailDOList = BeanUtils.toBean(createReqVO.getShipOrderDetailList(), ShipOrderDetailDO.class);
        shipOrderDetailDOList.forEach(shipOrderDetailDO -> shipOrderDetailDO.setShipOrderId(shipOrder.getId()));
        shipOrderDetailService.insertBatch(shipOrderDetailDOList);
        // 返回
        return shipOrder.getId();
    }

    @Override
    @DataPermission(enable = false)
    public void updateShipOrder(ShipOrderSaveReqVO updateReqVO) {
        // 校验存在
        validateShipOrderExists(updateReqVO.getId());
        // 更新
        ShipOrderDO updateObj = BeanUtils.toBean(updateReqVO, ShipOrderDO.class);
        shipOrderMapper.updateById(updateObj);
    }

    @Override
    @Transactional
    public void deleteShipOrder(Long id) {
        ShipOrderDO shipOrder = getShipOrder(id);
        if (shipOrder == null) {
            throw exception(SHIP_ORDER_NOT_EXISTS);
        }
        if (ShipOrderStatusEnum.RECEIVED.getStatus().equals(shipOrder.getStatus())) {
            throw exception(SHIP_ORDER_STATUS_CANNOT_DELETE);
        }
        PickTaskDO pickTask = pickTaskService.getPickTask(shipOrder.getCode());
        // 删除出库单记录
        shipOrderMapper.deleteById(id);
        // 删除出库单明细记录
        shipOrderDetailService.deleteShipOrderDetail(id);

        if (pickTask != null) {
            if (ShipOrderStatusEnum.TO_BE_PICKED.getStatus().equals(shipOrder.getStatus())
                    || ShipOrderStatusEnum.PICKING.getStatus().equals(shipOrder.getStatus())) {
                pickTaskService.deletePickTask(id);
                pickTaskMaterialService.deletePickTaskMaterial(id);
            }
            if (ShipOrderStatusEnum.PICK_OVER.getStatus().equals(shipOrder.getStatus())
                || ShipOrderStatusEnum.OUTING.getStatus().equals(shipOrder.getStatus())
                || ShipOrderStatusEnum.TRANSPORTING.getStatus().equals(shipOrder.getStatus())) {
                pickTaskMaterialDetailService.rollbackPickTaskMaterialBoxTray(id, shipOrder.getWarehouseId());
                pickTaskService.deletePickTask(id);
                pickTaskMaterialService.deletePickTaskMaterial(id);
                pickTaskMaterialDetailService.deletePickTaskMaterialDetail(id);
            }
            RedisUtil.removeAll(pickTask.getCode());
        }
    }

    private void validateShipOrderExists(Long id) {
        if (shipOrderMapper.selectById(id) == null) {
            throw exception(SHIP_ORDER_NOT_EXISTS);
        }
    }

    @Override
    @DataPermission(enable = false)
    public ShipOrderDO getShipOrder(Long id) {
        return shipOrderMapper.selectById(id);
    }

    @Override
    public PageResult<ShipOrderRespVO> getShipOrderPage(ShipOrderPageReqVO pageReqVO) {
        PageResult<ShipOrderDO> shipOrderDOPageResult = shipOrderMapper.selectPage(pageReqVO);
        if (CollUtil.isEmpty(shipOrderDOPageResult.getList())) {
            return PageResult.empty();
        }
        List<AdminUserRespDTO> userList = adminUserApi.getUserList();
        // 获取用户列表并构建 userId -> nickname 的映射
        Map<Long, String> userNicknameMap = userList.stream()
                .collect(Collectors.toMap(AdminUserRespDTO::getId, AdminUserRespDTO::getNickname));

        PageResult<ShipOrderRespVO> shipOrderRespVOPageResult = BeanUtils.toBean(shipOrderDOPageResult, ShipOrderRespVO.class);
        // 遍历 shipOrderRespVOPageResult.getList() 并赋值 creatorName
        shipOrderRespVOPageResult.getList().forEach(shipOrderRespVO -> {
            // 匹配 creator 对应的 nickname
            if (shipOrderRespVO.getCreator() != null && userNicknameMap.containsKey(Long.valueOf(shipOrderRespVO.getCreator()))) {
                shipOrderRespVO.setCreatorName(userNicknameMap.get(Long.valueOf(shipOrderRespVO.getCreator())));
            }
        });
        return shipOrderRespVOPageResult;
    }

    @Override
    public ShipOrderDO getShipOrder(String code) {
        return shipOrderMapper.selectOne(ShipOrderDO::getCode, code);
    }

    @Override
    public List<ShipOrderDO> getShipOrderListByStatus(List<String> status, String warehouseType) {
        LambdaQueryWrapperX<ShipOrderDO> query = new LambdaQueryWrapperX<>();
        query.in(ShipOrderDO::getStatus, status).eq(ShipOrderDO::getWarehouseType, warehouseType);
        return shipOrderMapper.selectList(query);
    }

    @Override
    public void ship(Long shipOrderId) {
        // 校验存在
        validateShipOrderExists(shipOrderId);
        ShipOrderDO shipOrder = getShipOrder(shipOrderId);
        if (WarehouseTypeEnum.TRIPARTITE.getType().equals(shipOrder.getWarehouseType())
            || MaterialModeConstants.IS_DIRECT_YES.equals(shipOrder.getIsDirect())) {
            shipOrder.setStatus(ShipOrderStatusEnum.OUT.getStatus());
            // 解绑托盘和RFID
            List<PickTaskMaterialDetailDO> pickTaskMaterialDetailList = pickTaskMaterialDetailService.getPickTaskMaterialDetailList(shipOrderId);
            Set<String> trayCodes = pickTaskMaterialDetailList.stream().map(PickTaskMaterialDetailDO::getTrayCode).collect(Collectors.toSet());
            rfidService.unbind(trayCodes);
        } else {
            shipOrder.setStatus(ShipOrderStatusEnum.OUTING.getStatus());
        }
        shipOrder.setOutingTime(LocalDateTime.now());
        shipOrderMapper.updateById(shipOrder);
    }

    @Override
    @Transactional
    public Long createManualShipOrder(ManualShipOrderSaveReqVO saveReqVO) {
        if (CollUtil.isEmpty(saveReqVO.getShipOrderDetailList())) {
            throw exception(SHIP_ORDER_TRIPARTITE_EMPTY);
        }
        AdminUserRespDTO user = adminUserApi.getUser(getLoginUserId());
        if (user.getWarehouseId() == null) {
            throw exception(ErrorCodeConstants.USER_NO_WAREHOUSE);
        }
        WarehouseDO warehouse = warehouseService.getWarehouse(user.getWarehouseId());
        if (warehouse == null) {
            throw exception(WAREHOUSE_NOT_EXISTS);
        }
        Long tripartiteWarehouseId = null;
        Long customerId = null;
        for (ManualShipOrderDetailSaveReqVO reqVO : saveReqVO.getShipOrderDetailList()) {
            List<MaterialDO> materials = materialService.getMaterialsByModeCode(user.getWarehouseId(), reqVO.getMaterialModeCode());
            if (CollUtil.isEmpty(materials)) {
                throw exception(SHIP_ORDER_TRIPARTITE_MATERIAL_NOT_ENOUGH, reqVO.getMaterialModeCode());
            }
            Integer notShipOverMaterialsCount = getNotShipOverMaterialsCount(user.getWarehouseId(), reqVO.getMaterialModeCode());
            if (reqVO.getShipCount() > materials.size() - notShipOverMaterialsCount) {
                throw exception(SHIP_ORDER_TRIPARTITE_MATERIAL_NOT_ENOUGH, reqVO.getMaterialModeCode());
            }
            MaterialModeDO materialMode = materialModeService.getMaterialMode(reqVO.getMaterialModeCode());
            if (materialMode == null) {
                throw exception(MATERIAL_MODE_NOT_EXISTS);
            }
            if (ObjUtil.isNull(tripartiteWarehouseId) || ObjUtil.isNull(customerId)) {
                tripartiteWarehouseId = materialMode.getTripartiteWarehouseId();
                customerId = materialMode.getCustomerId();
            } else {
                if (!tripartiteWarehouseId.equals(materialMode.getTripartiteWarehouseId())
                        || !customerId.equals(materialMode.getCustomerId())) {
                    throw exception(SHIP_ORDER_MATERIALMODE_NOT_SAME_WAREHOUSE_CUSTOMER);
                }
            }

        }
        ShipOrderSaveReqVO shipOrderSaveReqVO = new ShipOrderSaveReqVO();
        shipOrderSaveReqVO.setCode(orderNoRedisDAO.generate(OrderNoRedisDAO.SHIP_ORDER_NO_PREFIX, RedisKeyConstants.SHIP_ORDER_NO));
        shipOrderSaveReqVO.setWarehouseId(warehouse.getId());
        shipOrderSaveReqVO.setWarehouseType(warehouse.getWarehouseType());
        shipOrderSaveReqVO.setDeptId(warehouse.getDeptId());
        shipOrderSaveReqVO.setFromWarehouseCode(warehouse.getCode());
        ManualShipOrderDetailSaveReqVO firstShipOrderDetail = saveReqVO.getShipOrderDetailList().get(0);
        if (WarehouseTypeEnum.PRODUCTION.getType().equals(warehouse.getWarehouseType())) {
            if (StrUtil.isEmpty(saveReqVO.getToCustomerCode())) {
                MaterialModeDO materialMode = materialModeService.getMaterialMode(firstShipOrderDetail.getMaterialModeCode(), MaterialModeConstants.IS_DIRECT_NO);
                if (materialMode == null) {
                    throw exception(MATERIAL_MODE_NOT_EXISTS_DIRECT_NO);
                }
                WarehouseDO toWarehouse = warehouseService.getWarehouse(materialMode.getTripartiteWarehouseId());
                if (toWarehouse == null) {
                    throw exception(WAREHOUSE_NOT_EXISTS);
                }
                CustomerDO customer = customerService.getCustomer(materialMode.getCustomerId());
                if (customer == null) {
                    throw exception(CUSTOMER_NOT_EXISTS);
                }
                shipOrderSaveReqVO.setToWarehouseCode(toWarehouse.getCode());
                shipOrderSaveReqVO.setToWarehouseId(toWarehouse.getId());
                shipOrderSaveReqVO.setCustomerId(customer.getId());
                shipOrderSaveReqVO.setCustomerCode(customer.getCode());
                shipOrderSaveReqVO.setIsDirect(MaterialModeConstants.IS_DIRECT_NO);
            } else {
                MaterialModeDO materialMode = materialModeService.getMaterialMode(firstShipOrderDetail.getMaterialModeCode(), MaterialModeConstants.IS_DIRECT_YES);
                if (materialMode == null) {
                    throw exception(MATERIAL_MODE_NOT_EXISTS_DIRECT_YES);
                }
                CustomerDO customer = customerService.getCustomer(saveReqVO.getToCustomerCode());
                if (customer == null) {
                    throw exception(CUSTOMER_NOT_EXISTS);
                }
                if (!materialMode.getCustomerId().equals(customer.getId())) {
                    throw exception(SHIP_ORDER_CUSTOMER_MATERIALMODE_ERROR);
                }
                shipOrderSaveReqVO.setCustomerId(customer.getId());
                shipOrderSaveReqVO.setCustomerCode(customer.getCode());
                shipOrderSaveReqVO.setIsDirect(MaterialModeConstants.IS_DIRECT_YES);
            }
        } else if (WarehouseTypeEnum.TRIPARTITE.getType().equals(shipOrderSaveReqVO.getWarehouseType())) {
            MaterialModeDO materialMode = materialModeService.getMaterialMode(firstShipOrderDetail.getMaterialModeCode(), MaterialModeConstants.IS_DIRECT_NO);
            if (materialMode == null) {
                throw exception(MATERIAL_MODE_NOT_EXISTS);
            }
            CustomerDO customer = customerService.getCustomer(materialMode.getCustomerId());
            if (customer == null) {
                throw exception(CUSTOMER_NOT_EXISTS);
            }
            shipOrderSaveReqVO.setCustomerId(customer.getId());
            shipOrderSaveReqVO.setCustomerCode(customer.getCode());
            shipOrderSaveReqVO.setIsDirect(MaterialModeConstants.IS_DIRECT_NO);
        }
        shipOrderSaveReqVO.setStatus(ShipOrderStatusEnum.NEW.getStatus());
        ShipOrderDO shipOrder = BeanUtils.toBean(shipOrderSaveReqVO, ShipOrderDO.class);
        shipOrderMapper.insert(shipOrder);

        // 插入发货单明细
        List<ShipOrderDetailDO> shipOrderDetailDOList = new ArrayList<>();
        for (ManualShipOrderDetailSaveReqVO manualShipOrderSaveReqVO : saveReqVO.getShipOrderDetailList()) {
            ShipOrderDetailDO shipOrderDetailDO = new ShipOrderDetailDO();
            shipOrderDetailDO.setMaterialCount(manualShipOrderSaveReqVO.getShipCount());
            shipOrderDetailDO.setModeCode(manualShipOrderSaveReqVO.getMaterialModeCode());
            shipOrderDetailDO.setShipOrderId(shipOrder.getId());
            shipOrderDetailDOList.add(shipOrderDetailDO);
        }
        shipOrderDetailService.insertBatch(shipOrderDetailDOList);
        // 返回
        return shipOrder.getId();
    }

    @Override
    public Integer getNotShipOverMaterialsCount(Long warehouseId, String materialModeCode) {
        // 查询所有待出库的出库单
        List<ShipOrderDO> shipOrderDOS = shipOrderMapper.selectList(new LambdaQueryWrapperX<ShipOrderDO>()
                .in(ShipOrderDO::getStatus, ListUtil.toList(
                        ShipOrderStatusEnum.TO_BE_PICKED.getStatus(),
                        ShipOrderStatusEnum.PICKING.getStatus(),
                        ShipOrderStatusEnum.PICK_OVER.getStatus()))
                .eqIfPresent(ShipOrderDO::getWarehouseId, warehouseId));
        if (CollUtil.isEmpty(shipOrderDOS)) {
            return 0;
        }
        // 获取出库单ID集合
        Set<Long> collectShipOrderIdSet = shipOrderDOS.stream().map(ShipOrderDO::getId).collect(Collectors.toSet());
        // 查询出库单明细
        List<ShipOrderDetailDO> shipOrderDetailDOS = shipOrderDetailMapper.selectList(new LambdaQueryWrapperX<ShipOrderDetailDO>()
                .eqIfPresent(ShipOrderDetailDO::getModeCode, materialModeCode)
                .inIfPresent(ShipOrderDetailDO::getShipOrderId, collectShipOrderIdSet));
        if (CollUtil.isEmpty(shipOrderDetailDOS)) {
            return 0;
        }
        return shipOrderDetailDOS.stream()
                .mapToInt(ShipOrderDetailDO::getMaterialCount)
                .sum();
    }

    @Override
    public List<WarehouseMaterialCount> getWarehouseMaterialCountList() {
        return shipOrderMapper.getTripartiteMaterialChart();
    }

    @Override
    public Integer getProductWarehouseHistoryCount(boolean isToday) {
        return shipOrderMapper.getProductWarehouseHistoryCount(isToday);
    }

    @Override
    public Integer getProductWarehouseMonthCount() {
        return shipOrderMapper.getProductWarehouseMonthCount();
    }

    @Override
    @MaterialLog(operateType = MaterialOperateTypeEnum.PROD_OUT)
    public void materialLogProductionShip(List<Long> materialIds) {
        MaterialLogUtils.setMaterialInfo(materialIds);
    }

    @Override
    @MaterialLog(operateType = MaterialOperateTypeEnum.THIRD_OUT)
    public void materialLogTripartiteShip(List<Long> materialIds) {
        MaterialLogUtils.setMaterialInfo(materialIds);
    }

    @Override
    @DataPermission(enable = false)
    public List<ShipOrderTrayRespVO> getTrayList(Long shipOrderId, Long warehouseId) {
        return shipOrderMapper.getTrayList(shipOrderId, warehouseId);
    }
}
